﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using UCL.Powersource;

namespace PowersourceUIToolkit
{

    /// <summary>
    /// Hosts attached properties related to View models.
    /// </summary>
    public static class View
    {
        static readonly ILog Log = LogManager.GetLog(typeof(View));
        static readonly ContentPropertyAttribute DefaultContentProperty = new ContentPropertyAttribute("Content");

        /// <summary>
        /// The default View Context.
        /// </summary>
        public static readonly object DefaultContext = new object();

        /// <summary>
        /// A dependency property which allows the framework to track whether a certain element has already been loaded in certain scenarios.
        /// </summary>
        public static readonly DependencyProperty IsLoadedProperty =
            DependencyProperty.RegisterAttached(
                "IsLoaded",
                typeof(bool),
                typeof(View),
                new PropertyMetadata(false)
                );

        /// <summary>
        /// A dependency property which marks an element as a name scope root.
        /// </summary>
        public static readonly DependencyProperty IsScopeRootProperty =
            DependencyProperty.RegisterAttached(
                "IsScopeRoot",
                typeof(bool),
                typeof(View),
                new PropertyMetadata(false)
                );

        /// <summary>
        /// A dependency property which allows the override of convention application behavior.
        /// </summary>
        public static readonly DependencyProperty ApplyConventionsProperty =
            DependencyProperty.RegisterAttached(
                "ApplyConventions",
                typeof(bool?),
                typeof(View),
                null
                );

        /// <summary>
        /// A dependency property for assigning a Context to a particular portion of the UI.
        /// </summary>
        public static readonly DependencyProperty ContextProperty =
            DependencyProperty.RegisterAttached(
                "Context",
                typeof(object),
                typeof(View),
                new PropertyMetadata(OnContextChanged)
                );

        /// <summary>
        /// A dependency property for attaching a model to the UI.
        /// </summary>
        public static DependencyProperty ModelProperty =
            DependencyProperty.RegisterAttached(
                "Model",
                typeof(IViewModel),
                typeof(View),
                new PropertyMetadata(OnModelChanged)
                );

        /// <summary>
        /// Used by the framework to indicate that this element was generated.
        /// </summary>
        public static readonly DependencyProperty IsGeneratedProperty =
            DependencyProperty.RegisterAttached(
                "IsGenerated",
                typeof(bool),
                typeof(View),
                new PropertyMetadata(false, null)
                );

        /// <summary>
        /// Executes the handler immediately if the element is loaded, otherwise wires itForFE to the Loaded event.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="handler">The handler.</param>
        /// <returns>true if the handler was executed immediately; false otherwise</returns>
        public static bool ExecuteOnLoad(FrameworkElement element, RoutedEventHandler handler)
        {

            if (element.IsLoaded)
            {
                handler(element, new RoutedEventArgs());
                return true;
            }
            else
            {
                RoutedEventHandler loaded = null;
                loaded = (s, e) =>
                {
                    handler(s, e);
                    element.Loaded -= loaded;
                };

                element.Loaded += loaded;
                return false;
            }
        }



        /// <summary>
        /// Gets the convention application behavior.
        /// </summary>
        /// <param name="d">The element the property is attached to.</param>
        /// <returns>Whether or not to apply conventions.</returns>
        public static bool? GetApplyConventions(DependencyObject d)
        {
            return (bool?)d.GetValue(ApplyConventionsProperty);
        }

        /// <summary>
        /// Sets the convention application behavior.
        /// </summary>
        /// <param name="d">The element to attach the property to.</param>
        /// <param name="value">Whether or not to apply conventions.</param>
        public static void SetApplyConventions(DependencyObject d, bool? value)
        {
            d.SetValue(ApplyConventionsProperty, value);
        }

        /// <summary>
        /// Sets the model.
        /// </summary>
        /// <param name="d">The element to attach the model to.</param>
        /// <param name="value">The model.</param>
        public static void SetModel(DependencyObject d, object value)
        {
            d.SetValue(ModelProperty, value);
        }

        /// <summary>
        /// Gets the model.
        /// </summary>
        /// <param name="d">The element the model is attached to.</param>
        /// <returns>The model.</returns>
        public static object GetModel(DependencyObject d)
        {
            return d.GetValue(ModelProperty);
        }

        /// <summary>
        /// Gets the Context.
        /// </summary>
        /// <param name="d">The element the Context is attached to.</param>
        /// <returns>The Context.</returns>
        public static object GetContext(DependencyObject d)
        {
            return d.GetValue(ContextProperty);
        }

        /// <summary>
        /// Sets the Context.
        /// </summary>
        /// <param name="d">The element to attach the Context to.</param>
        /// <param name="value">The Context.</param>
        public static void SetContext(DependencyObject d, object value)
        {
            d.SetValue(ContextProperty, value);
        }

        static void OnModelChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs args)
        {
            if (args.OldValue == args.NewValue)
            {
                return;
            }

            var newVM=args.NewValue  as IViewModel;
            if (newVM == null && args.NewValue != null)
            {
                Log.Warn("Skipping OnModelChanged automatic View binding as args.NewValue is not a ViewModel");
                return;
            }
            
            

            if (newVM != null)
            {
                var context = GetContext(targetLocation);
                var view = ViewLocator.CreateOrGetDefaultView(newVM);
                SetContentProperty(targetLocation, view);
                ViewModelBinder.Bind(newVM, view, context);
            }
            else
            {
                SetContentProperty(targetLocation, args.NewValue);
            }
        }

        static void OnContextChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue == e.NewValue)
            {
                return;
            }

            Log.Warn("Changing Context not supported");

            //var model = GetModel(targetLocation);
            //if (model == null)
            //{
            //    return;
            //}

            //var View = ViewLocator.CreateOrGetDefaultView(model, targetLocation, e.NewValue);

            //SetContentProperty(targetLocation, View);
            //ViewModelBinder.Bind(model, View, e.NewValue);
        }

        static void SetContentProperty(object targetLocation, object view)
        {
            var fe = view as FrameworkElement;
            if (fe != null && fe.Parent != null)
            {
                SetContentPropertyCore(fe.Parent, null);
            }

            SetContentPropertyCore(targetLocation, view);
        }

        static void SetContentPropertyCore(object targetLocation, object view)
        {
            try
            {
                var type = targetLocation.GetType();
                var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
                                          .FirstOrDefault() ?? DefaultContentProperty;

                type.GetProperty(contentProperty.Name)
                    .SetValue(targetLocation, view, null);
            }
            catch (Exception e)
            {
                Log.Error(e);
            }
        }
    }
}

